//
//  QNCentralManager.m
//  QNCentralModule
//
//  Created by donyau on 2018/9/12.
//  Copyright © 2018年 Yolanda. All rights reserved.
//

#import "QNCentralManager.h"
#import "NSError+QNCentral.h"
#import "CBCentralManager+QNOverTime.h"
#import "QNCentralManager+Log.h"
#import "QNCategory.h"
#include <sys/time.h>

@interface QNCentralManager ()<CBCentralManagerDelegate>

@property (nonatomic, strong) CBCentralManager *centralManager;

@property (nonatomic, strong) NSHashTable<id<QNCentralManagerDelegate>> *delegates;

@property (nonatomic, weak) id<CBCentralManagerDelegate> centralDelegateCache;

@end

@implementation QNCentralManager
static QNCentralManager *manager = nil;
static BOOL _qnCentralDebugFlag = NO;
static BOOL _qnCentralLogPrefixFlag = NO;
static BOOL _qnCentralSDKFlag = NO;
static NSString *_qnCentralLogHeader = nil;
static BOOL _qnCloseAdvLogFlag = NO;

+ (void)setLogHeader:(NSString *)logHeader {
    _qnCentralLogHeader = logHeader;
}

+ (NSString *)logHeader {
    return _qnCentralLogHeader;
}

+ (BOOL)closeAdvLogFlag {
    return _qnCloseAdvLogFlag;
}

+ (void)setCloseAdvLogFlag:(BOOL)closeAdvLogFlag {
    _qnCloseAdvLogFlag = closeAdvLogFlag;
}

+ (void)setDebug:(BOOL)debug {
    _qnCentralDebugFlag = debug;
}

+ (BOOL)debug {
    return _qnCentralDebugFlag;
}

+ (void)setLogPrefix:(BOOL)logPrefix {
    _qnCentralLogPrefixFlag = logPrefix;
}

+ (BOOL)logPrefix {
    return _qnCentralLogPrefixFlag;
}

+ (void)setSdkFlag:(BOOL)sdkFlag {
    _qnCentralSDKFlag = sdkFlag;
}

+ (BOOL)sdkFlag {
    return _qnCentralSDKFlag;
}

#pragma mark - 懒加载
- (NSHashTable<id<QNCentralManagerDelegate>> *)delegates {
    if (_delegates == nil) {
        _delegates = [NSHashTable<id<QNCentralManagerDelegate>> hashTableWithOptions:NSPointerFunctionsWeakMemory];
    }
    return _delegates;
}

#pragma mark -
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [super allocWithZone:zone];
    });
    return manager;
}

- (instancetype)initCentralManager:(CBCentralManager *)centralManager showPowerAlertFlag:(BOOL)showPowerAlert queue:(dispatch_queue_t)queue {
    if (self = [super init]) {
        if (centralManager == nil) {
            if (self.centralManager == nil) {//初始化一次后不再覆盖
                self.centralManager =  [[CBCentralManager alloc] initWithDelegate:self queue:queue options:@{CBCentralManagerOptionShowPowerAlertKey : [NSNumber numberWithBool:showPowerAlert]}];
                [self setValue:[NSNumber numberWithBool:showPowerAlert] forKeyPath:@"showPowerAlertFlag"];
            }
        }else {
            if (self.centralManager != nil) {
                [self delegateSelectorOnMainThread:@selector(centralManagerConnectionPeripheral) callBlock:^(id<QNCentralManagerDelegate> _Nonnull delegate) {
                    CBPeripheral *peripheral = [delegate centralManagerConnectionPeripheral];
                    if (peripheral.state == CBPeripheralStateConnecting || peripheral.state == CBPeripheralStateConnected) {
                        [self.centralManager cancelPeripheralConnection:peripheral];
                        [self centralManager:self.centralManager didDisconnectPeripheral:peripheral error:[NSError errorWithDomain:QNCentralDomain code:11000 userInfo:@{NSLocalizedDescriptionKey: @"rebuild centralManager disconnect"}]];
                    }
                }];
            }
            
            [self qnRemoveObserverBlocksForKeyPath:@"centralManager.delegate"];
            self.centralManager = centralManager;
            if (self.centralManager.delegate) self.centralDelegateCache = self.centralManager.delegate;
            self.centralManager.delegate = self;
            __weak typeof(self) weakSelf = self;
            [self qnAddObserverBlockForKeyPath:@"centralManager.delegate" block:^(id  _Nonnull obj, id  _Nullable oldVal, id  _Nullable newVal) {
                if (newVal != oldVal && newVal != weakSelf) {
                    if (newVal) weakSelf.centralDelegateCache = newVal;
                    weakSelf.centralManager.delegate = weakSelf;
                }
            }];
        }
    }
    return self;
}

- (void)dealloc {
    [self qnRemoveObserverBlocksForKeyPath:@"centralManager.delegate"];
}

#pragma mark - delegate赋值
- (void)setDelegate:(id<QNCentralManagerDelegate>)delegate {
    [self.delegates addObject:delegate];
}

- (BOOL)isScanning {
    if (@available(iOS 9.0, *)) {
        return self.centralManager.isScanning;
    }else {
        return self.scanningFlag;
    }
}

#pragma mark - 扫描状态
- (void)updateScanStateWithScaningFlag:(BOOL)scaningFlag {
    self.scanningFlag = scaningFlag;
}

#pragma mark - 系统蓝牙状态更新
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    if ([self.centralDelegateCache respondsToSelector:@selector(centralManagerDidUpdateState:)]) {
        [self.centralDelegateCache centralManagerDidUpdateState:central];
    }
    QNBlueToothState bleState = [self currentBlueTooth];
    [self centralLogMessage:[NSString stringWithFormat:@"系统蓝牙状态更新: %ld",(long)bleState]];

    if (bleState != QNBlueToothStatePoweredOn) [self updateScanStateWithScaningFlag:NO];
    
    [self delegateSelectorOnMainThread:@selector(centralManagerBlueToothUpdateState:) callBlock:^(id<QNCentralManagerDelegate>  _Nonnull delegate) {
        [delegate centralManagerBlueToothUpdateState:bleState];
    }];

}

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
    if ([self.centralDelegateCache respondsToSelector:@selector(centralManager:didDiscoverPeripheral:advertisementData:RSSI:)]) {
        [self.centralDelegateCache centralManager:central didDiscoverPeripheral:peripheral advertisementData:advertisementData RSSI:RSSI];
    }
    if (!QNCentralManager.closeAdvLogFlag) {
        [self centralLogMessage:[self getDiscoverDeviceInfoWithPeripheral:peripheral advertisementData:advertisementData RSSI:RSSI]];
    }
    self.isDiscoverAdvertisementData = YES;
    [self delegateSelectorOnMainThread:@selector(centralDiscoverPeripheral:advertisementData:rssi:) callBlock:^(id<QNCentralManagerDelegate>  _Nonnull delegate) {
        [delegate centralDiscoverPeripheral:peripheral advertisementData:advertisementData rssi:RSSI];
    }];
}

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    [self centralLogMessage:[NSString stringWithFormat:@"已连接[%@][%d]", peripheral.name, peripheral.qnCompanyFlag]];
    if (peripheral.qnCompanyFlag == NO && self.centralDelegateCache) {
        if ([self.centralDelegateCache respondsToSelector:@selector(centralManager:didConnectPeripheral:)]) {
            [self.centralDelegateCache centralManager:central didConnectPeripheral:peripheral];
        }
        return;
    }
    [self delegateSelectorOnMainThread:@selector(centralManagerPeripheralUpdateState:peripheral:errCode:error:) callBlock:^(id<QNCentralManagerDelegate>  _Nonnull delegate) {
        [delegate centralManagerPeripheralUpdateState:QNPeripheralStateConnected peripheral:peripheral errCode:QNBleErrCodeUnException error:nil];
    }];
    [peripheral discoverServices:nil];
}

- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    [self centralLogMessage:[NSString stringWithFormat:@"连接失败[%@][%d][%@]", peripheral.name, peripheral.qnCompanyFlag,error.localizedDescription]];
    if (peripheral.qnCompanyFlag == NO && self.centralDelegateCache) {
        if ([self.centralDelegateCache respondsToSelector:@selector(centralManager:didFailToConnectPeripheral:error:)]) {
            [self.centralDelegateCache centralManager:central didFailToConnectPeripheral:peripheral error:error];
        }
        return;
    }
    peripheral.qnConnectFailFlag = YES;
    [central cancelPeripheralConnection:peripheral];
    [self delegateSelectorOnMainThread:@selector(centralManagerPeripheralUpdateState:peripheral:errCode:error:) callBlock:^(id<QNCentralManagerDelegate>  _Nonnull delegate) {
        QNBleErrCode errCode = QNBleErrCodeConnectSystem;
        if (error.code == QNBleErrCodeConnectOvertime || error.code == QNBleErrCodeStateClose) {
            errCode = error.code;
        }
        [delegate centralManagerPeripheralUpdateState:QNPeripheralStateConnectFail peripheral:peripheral errCode:errCode error:error];
    }];
}

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    [self centralLogMessage:[NSString stringWithFormat:@"已断开连接[%@][%d][%@]", peripheral.name, peripheral.qnCompanyFlag,error.localizedDescription]];

    if (peripheral.qnCompanyFlag == NO && self.centralDelegateCache) {
        if ([self.centralDelegateCache respondsToSelector:@selector(centralManager:didDisconnectPeripheral:error:)]) {
            [self.centralDelegateCache centralManager:central didDisconnectPeripheral:peripheral error:error];
        }
        return;
    }
    if (peripheral.qnConnectFailFlag) {
        peripheral.qnConnectFailFlag = NO;
        return;
    }

    [self delegateSelectorOnMainThread:@selector(centralManagerPeripheralUpdateState:peripheral:errCode:error:) callBlock:^(id<QNCentralManagerDelegate>  _Nonnull delegate) {
        [delegate centralManagerPeripheralUpdateState:QNPeripheralStateDisconected peripheral:peripheral errCode:QNBleErrCodeUnException error:error];
    }];
}

#pragma mark 获取当前系统蓝牙的状态
- (QNBlueToothState)currentBlueTooth {
    QNBlueToothState bleState = QNBlueToothStateUnknown;
    if (@available(iOS 10.0, *)) {
        switch (self.centralManager.state) {
            case CBManagerStateResetting:
                bleState = QNBlueToothStateResetting;
                break;
            case CBManagerStateUnsupported:
                bleState = QNBlueToothStateUnsupported;
                break;
            case CBManagerStateUnauthorized:
                bleState = QNBlueToothStateUnauthorized;
                break;
            case CBManagerStatePoweredOff:
                bleState = QNBlueToothStatePoweredOff;
                break;
            case CBManagerStatePoweredOn:
                bleState = QNBlueToothStatePoweredOn;
                break;
            default:
                bleState = QNBlueToothStateUnknown;
                break;
        }
    } else {
        switch (self.centralManager.state) {
            case CBCentralManagerStateResetting:
                bleState = QNBlueToothStateResetting;
                break;
            case CBCentralManagerStateUnsupported:
                bleState = QNBlueToothStateUnsupported;
                break;
            case CBCentralManagerStateUnauthorized:
                bleState = QNBlueToothStateUnauthorized;
                break;
            case CBCentralManagerStatePoweredOff:
                bleState = QNBlueToothStatePoweredOff;
                break;
            case CBCentralManagerStatePoweredOn:
                bleState = QNBlueToothStatePoweredOn;
                break;
            default:
                bleState = QNBlueToothStateUnknown;
                break;
        }
    }
    return bleState;
}

#pragma mark - 开始扫描
- (void)scanDeviceHandle:(QNCentralResponseBlock)block {
    QNBlueToothState state = [self currentBlueTooth];
    if (state == QNBlueToothStatePoweredOn) {
        [self centralLogMessage:@"启动设备扫描成功"];
        [self updateScanStateWithScaningFlag:YES];
        [self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES}];
        if (block) block(YES,nil);
        [self delegateSelectorOnMainThread:@selector(centralScanStateUpdate:) callBlock:^(id<QNCentralManagerDelegate>  _Nonnull delegate) {
            [delegate centralScanStateUpdate:QNCentralScanStart];
        }];
        return;
    }
    
    [self centralLogMessage:[NSString stringWithFormat:@"启动扫描设备失败,当前蓝牙状态为 %ld",(long)state]];
    
    if (block) block(NO,[self blueToothStateError]);
}

#pragma mark 停止扫描
- (void)stopScanDevice {
    [self centralLogMessage:@"停止扫描设备"];
    if ([self currentBlueTooth] == QNBlueToothStatePoweredOn) {
        [self.centralManager stopScan];
    }
    [self updateScanStateWithScaningFlag:NO];
    [self delegateSelectorOnMainThread:@selector(centralScanStateUpdate:) callBlock:^(id<QNCentralManagerDelegate>  _Nonnull delegate) {
        [delegate centralScanStateUpdate:QNCentralScanEnd];
    }];
    self.isDiscoverAdvertisementData = NO;
}

- (NSString *)getDiscoverDeviceInfoWithPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
    NSString *peripheralName = peripheral.name;
    NSData *manufacturerData = (NSData *)[advertisementData valueForKey:CBAdvertisementDataManufacturerDataKey];
    NSString *advLocalName = (NSString *)[advertisementData valueForKey:CBAdvertisementDataLocalNameKey];
    NSString *serviceUUID = nil;
    if ([advertisementData.allKeys containsObject:@"kCBAdvDataServiceUUIDs"]) {
        for (CBUUID *item in advertisementData[@"kCBAdvDataServiceUUIDs"]) {
            NSString *uuidStr = item.UUIDString;
            if (serviceUUID == nil) {
                serviceUUID = uuidStr;
            }else{
                serviceUUID = [NSString stringWithFormat:@"%@,%@",serviceUUID,uuidStr];
            }
        }
    }
    const Byte *byts = manufacturerData.bytes;
    NSString *str = @"";
    for (int i = 0; i < manufacturerData.length; i ++) {
        str = [NSString stringWithFormat:@"%@ %02x",str,byts[i]];
    }
    return [NSString stringWithFormat:@"discover device: advLocalName:[%@] blueToothName:[%@] manufacturerData:[%@] services:[%@][%ld]", advLocalName,peripheralName,str,serviceUUID, (long)RSSI.integerValue];
}

- (void)delegateSelectorOnMainThread:(SEL)selector callBlock:(void (^)(id<QNCentralManagerDelegate> _Nonnull delegate))block {
    if ([NSThread isMainThread]) {
        NSArray *temp = [self.delegates copy];
        NSEnumerator *enumerator = [temp objectEnumerator];
        id<QNCentralManagerDelegate> delegate = nil;
        while (delegate = [enumerator nextObject]) {
            if ([delegate respondsToSelector:selector] && block) block(delegate);
        }
    }else {
        dispatch_async(dispatch_get_main_queue(), ^{
            NSArray *temp = [self.delegates copy];
            NSEnumerator *enumerator = [temp objectEnumerator];
            id<QNCentralManagerDelegate> delegate = nil;
            while (delegate = [enumerator nextObject]) {
                if ([delegate respondsToSelector:selector] && block) block(delegate);
            }
        });
    }
}

@end

#pragma mark -
#pragma mark -
@implementation QNCentralManager (Addition)

#pragma mark - 重要信息的输出
- (void)logMessage:(NSString *)message {

    NSString *log = nil;
    if (QNCentralManager.logHeader) {
        if (QNCentralManager.logHeader.length > 0) {
            log = [NSString stringWithFormat:@"[** %@ %@ **] %@\n",[self currentTimeStr],QNCentralManager.logHeader,message];
        }else {
            log = [NSString stringWithFormat:@"[** %@ **] %@\n",[self currentTimeStr],message];
        }
    }else {
        log = message;
    }
    if (QNCentralManager.debug) {
        fprintf(stderr,"%s\n",[log UTF8String]);
    }
    [self delegateSelectorOnMainThread:@selector(centralManagerMessage:) callBlock:^(id<QNCentralManagerDelegate>  _Nonnull delegate) {
        [delegate centralManagerMessage:log];
    }];
}

- (NSString *)currentTimeStr {
    time_t t;
    struct tm *p;
    time(&t);
    p=localtime(&t);
        
    struct timeval tv;
    gettimeofday(&tv,NULL);
        
    return [NSString stringWithFormat:@"%04d-%02d-%02d_%02d:%02d:%02d:%03d",1900+p->tm_year,1+p->tm_mon,p->tm_mday,p->tm_hour,p->tm_min,p->tm_sec,tv.tv_usec / 1000];
}


#pragma mark - 蓝牙状态错误
- (NSError *)blueToothStateError {
    QNBlueToothState state = [self currentBlueTooth];
    NSError *error = nil;
    switch (state) {
        case QNBlueToothStateUnknown:
            error = [NSError errorWithCode:QNCentralStateUnknownError];
            break;
        case QNBlueToothStateResetting:
            error = [NSError errorWithCode:QNCentralStateResettingError];
            break;
        case QNBlueToothStateUnsupported:
            error = [NSError errorWithCode:QNCentralStateUnsupportedError];
            break;
        case QNBlueToothStateUnauthorized:
            error = [NSError errorWithCode:QNCentralStateUnauthorizedError];
            break;
        case QNBlueToothStatePoweredOff:
            error = [NSError errorWithCode:QNCentralStatePoweredOffError];
            break;
        default:
            break;
    }
    return error;
}

#pragma mark - 连接设备
- (void)connectPeripheral:(CBPeripheral *)peripheral internalModel:(NSString *)internalModel mac:(NSString *)mac connectConfig:(QNConnectConfig *)config response:(QNCentralResponseBlock)block {
    NSError *error = [self blueToothStateError];
    if (error != nil) {
        [self centralLogMessage:[NSString stringWithFormat:@"连接 %@ %@ %@设备失败，%@ ",peripheral.name,mac,internalModel,error.description]];
        if (block) block(NO,error);
        return;
    }
    [self centralLogMessage:[NSString stringWithFormat:@"去连接[%@][%@][%@]的设备，当前设备状态为[%ld], connectOverTime=[%f]",peripheral.name,mac,internalModel,(long)peripheral.state, config.connectOverTime]];
    if (@available(iOS 9.0, *)) {
        //2018-11-28 出现设备一直处于正在断开的状态
        if (peripheral.state != CBPeripheralStateDisconnected && peripheral.state != CBPeripheralStateDisconnecting) {
            [self centralLogMessage:[NSString stringWithFormat:@"连接 %@ %@ %@的设备失败，当前设备状态为 %ld ",peripheral.name,mac,internalModel,(long)peripheral.state]];
            if (block) block(NO,[NSError errorWithCode:QNCentralStatePeripheralHasConnectError]);
            return;
        }
    }else {
        if (peripheral.state != CBPeripheralStateDisconnected) {
            [self centralLogMessage:[NSString stringWithFormat:@"连接 %@ %@ %@的设备失败，当前设备状态为 %ld ",peripheral.name,mac,internalModel,(long)peripheral.state]];
            if (block) block(NO,[NSError errorWithCode:QNCentralStatePeripheralHasConnectError]);
            return;
        }
    }
    
    NSMutableDictionary *configDic = [NSMutableDictionary dictionary];
    [configDic setObject:[NSNumber numberWithBool:config.notifyOnConnection] forKey:CBConnectPeripheralOptionNotifyOnConnectionKey];
    [configDic setObject:[NSNumber numberWithBool:config.notifyOnDisconnection] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey];
    [configDic setObject:[NSNumber numberWithBool:config.notifyOnNotification] forKey:CBConnectPeripheralOptionNotifyOnNotificationKey];
    [configDic setObject:[NSNumber numberWithBool:config.notifyOnNotification] forKey:CBConnectPeripheralOptionNotifyOnNotificationKey];

    
    [self centralLogMessage:[NSString stringWithFormat:@"开始连接 %@ %@ %@设备",peripheral.name,mac,internalModel]];
    [self delegateSelectorOnMainThread:@selector(centralManagerPeripheralUpdateState:peripheral:errCode:error:) callBlock:^(id<QNCentralManagerDelegate>  _Nonnull delegate) {
        [delegate centralManagerPeripheralUpdateState:QNPeripheralStateConnecting peripheral:peripheral errCode:QNBleErrCodeUnException error:nil];
    }];
    peripheral.qnCompanyFlag = YES;
    [self.centralManager connectPeripheral:peripheral options:configDic];
    if (config.connectOverTime > 0.0001) {
        [peripheral qnStartConnectTimerCentralManager:self.centralManager inTimeInterval:config.connectOverTime];
    }
    if (block) block(YES,nil);
}

#pragma mark 断开设备连接
- (void)cancelConnectDevicePeripheral:(CBPeripheral *)peripheral logCode:(NSString *)logCode response:(QNCentralResponseBlock)block{
    if (logCode == nil) {
        [self centralLogMessage:[NSString stringWithFormat:@"发起断开 %@ 设备[%ld]",peripheral.name, (long)peripheral.state]];
    } else {
        [self centralLogMessage:[NSString stringWithFormat:@"%@: 发起断开 %@ 设备[%ld]",logCode,peripheral.name,  (long)peripheral.state]];
    }
    if (peripheral == nil) {
        if (block) block(YES,nil);
        return;
    }
    BOOL vail = [self currentBlueTooth] == QNBlueToothStatePoweredOn;
    if (@available(iOS 9.0, *)) {
        vail =  vail && peripheral.state != CBPeripheralStateDisconnected && peripheral.state != CBPeripheralStateDisconnecting;
    } else {
        vail =  vail && peripheral.state != CBPeripheralStateDisconnected;
    }
    if (vail) {
        [self.centralManager cancelPeripheralConnection:peripheral];
    }
    if (block) block(YES,nil);
}

- (void)cancelConnectDevicePeripheral:(CBPeripheral *)peripheral response:(QNCentralResponseBlock)block {
    [self cancelConnectDevicePeripheral:peripheral logCode:nil response:block];
}

#pragma mark 查找系统已经配对的设备
- (NSArray<CBPeripheral *> *)findConnectedPeripheralFromSystemWithServices:(NSArray<CBUUID *> *)serviceUUIDs {
    if (serviceUUIDs == nil) {
        return [NSArray array];
    }
    return [self.centralManager retrieveConnectedPeripheralsWithServices:serviceUUIDs];
}
@end
